from typing import List, Dict, Set, Optional, Tuple
import os
import glob
import re
import subprocess
import tempfile

# List of common C/C++ standard library functions to ignore in local analysis
STANDARD_C_FUNCTIONS = {
    # C/C++ standard library functions
    'printf', 'scanf', 'malloc', 'free', 'realloc', 'calloc',
    'strlen', 'strcpy', 'strncpy', 'strcmp', 'strncmp',
    'memcpy', 'memset', 'memmove', 'memcmp',
    'fopen', 'fclose', 'fread', 'fwrite', 'fprintf',
    'new', 'delete', 'sizeof', 'typeof',
    'assert', 'exit', 'abort',
    
    # GPTL timing functions
    'GPTLstart', 'GPTLstop', 'GPTLsetoption', 'GPTLinitialize', 'GPTLfinalize',
    'TIMERSTART', 'TIMEREND', 'DURATION_ms',
    
    # CUDA/HIP block configuration
    'threadsPerBlock', 'threadsPerBlock1', 'threadsPerBlock2',
    'numBlocks', 'numBlocks1', 'numBlocks2',
    
    # Common math functions
    'min', 'max', 'sqrt', 'ceil', 'floor', 'abs', 'pow', 'exp', 'log',
    
    # GPU synchronization
    'hipDeviceSynchronize', 'cudaDeviceSynchronize', 'hipblasSetPointerMode',
    'hipSetDevice', 'hipblasCreate',
    
    # Other common functions to filter
    'CHECK', 'svrasr', 'MPI_Comm_free', 'MPI_Finalize', 'MPI_Comm_size',
    'MPI_Init', 'MPI_Comm_split', 'MPI_Allreduce', 'hipblasSdot'
}

class FunctionNode:
    """Represents a function or method in C/C++ code."""
    def __init__(self, name: str, namespace: str = '', class_name: Optional[str] = None):
        self.name = name
        self.namespace = namespace
        self.class_name = class_name
        self.full_name = self._build_full_name()
        self.calls = set()  # Functions this function calls
        self.called_by = set()  # Functions that call this function
        self.source_code = ""
        self.lineno = 0
        self.end_lineno = 0
        self.conditional_calls = {}  # Maps called functions to conditions
        self.is_conditional = False  # Whether this function is conditionally called

    def _build_full_name(self) -> str:
        """Build the full name of the function using namespace and class name if available."""
        components = []
        if self.namespace:
            components.append(self.namespace)
        if self.class_name:
            components.append(self.class_name)
        components.append(self.name)
        return "::".join(components)

    def __repr__(self):
        return self.full_name

    def add_call(self, func_node: 'FunctionNode', condition: Optional[str] = None):
        """
        Add a function call relationship, optionally with a condition.
        
        Args:
            func_node: The function being called
            condition: Optional condition under which the function is called
        """
        self.calls.add(func_node)
        func_node.called_by.add(self)
        
        # Track conditional calls
        if condition:
            if func_node not in self.conditional_calls:
                self.conditional_calls[func_node] = []
            self.conditional_calls[func_node].append(condition)
            func_node.is_conditional = True

class CppCodeParser:
    """Parses C/C++ code and builds function call relationships."""
    def __init__(self, src_directories: List[str], include_paths: List[str] = None, 
                 is_recursive: bool = True, local_only: bool = True):
        self.src_directories = src_directories
        self.include_paths = include_paths or []
        self.is_recursive = is_recursive
        self.local_only = local_only
        self.functions: Dict[str, FunctionNode] = {}
        self.compilation_units: Dict[str, List[str]] = {}  # Maps compilation units to function names
        self.entry_points: List[str] = []
        self.debug_mode = False
        
        # Regular expressions for C/C++ parsing
        # Enhanced function definition pattern with better handling of parameter types and arrays
        self.function_def_pattern = re.compile(
            r'(?P<return_type>(?:[\w:<>]+(?:\s*[*&])*\s+)+|void\s+)'  # Return type - improved to handle templates
            r'(?P<name>[\w:~]+)\s*'                    # Function name
            r'\((?P<params>[^{;]*?(?:=\s*[^{;,]*?(?:,|$))?[^{;]*?)\)\s*'  # Better parameter handling including defaults
            r'(?P<qualifiers>(?:\s*(?:const|noexcept|override|final|= \s*0|\{\s*0\s*\}|throw\([^)]*\)))*)\s*'  # Function qualifiers
            r'(?P<body>\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}[^{}]*)*\})',  # Function body - captures nested braces
            re.DOTALL
        )
        
        # Add a specific pattern for functions with default parameters
        self.default_param_pattern = re.compile(
            r'(?P<return_type>(?:[\w:<>]+(?:\s*[*&])*\s+)+|void\s+)'  # Return type
            r'(?P<name>[\w:~]+)\s*'                    # Function name
            r'\((?P<params>[^{;]*?(?:=\s*[^{;,]*?(?:,|$))[^{;]*?)\)\s*'  # Parameters with at least one default value
            r'(?:\s*(?:const|noexcept|override|final|= \s*0|\{\s*0\s*\}|throw\([^)]*\)))*\s*'  # Qualifiers
            r'(?P<body>\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}[^{}]*)*\})',  # Function body
            re.DOTALL
        )
        
        # Improved pattern specifically for C/C++ main function with various parameter formats
        self.main_function_pattern = re.compile(
            r'int\s+main\s*\('                         # "int main(" is standard for C/C++
            r'(?:\s*int\s+\w+\s*,\s*char\s*\*+\s*\w+(?:\[\s*\])?|'  # int argc, char *argv[] or char **argv
            r'\s*int\s+\w+\s*,\s*char\s*\*+\s*\*+\s*\w+|'          # int argc, char **argv or char ***argv
            r'\s*void\s*|'                                          # void
            r'\s*int\s+\w+\s*|'                                     # just int argc
            r'\s*)'                                                 # or empty
            r'\)\s*'                                               # closing parenthesis
            r'(?P<body>\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}[^{}]*)*\})',  # Function body
            re.DOTALL
        )
        
        # Also add an additional simplified pattern just to match the exact main function found in gcr_rand/main.c
        self.main_pattern_simple = re.compile(
            r'int\s+main\s*\(\s*int\s+\w+\s*,\s*char\s*\*\s*\w+\[\s*\]\s*\)\s*\{',
            re.DOTALL
        )
        
        # Improved function call pattern to better detect function calls in code
        self.function_call_pattern = re.compile(
            r'(?<![a-zA-Z0-9_])'                  # Not preceded by identifier character (negative lookbehind)
            r'(?P<n>[\w:~]+)'                   # Function name, including namespace/class scope
            r'\s*\('                            # Opening parenthesis with optional whitespace
            r'(?P<args>[^;]*?)'                  # Arguments (non-greedy)
            r'\)',                               # Closing parenthesis
            re.DOTALL
        )
        
        # New pattern for method calls on objects
        self.method_call_pattern = re.compile(
            r'(?P<obj>[\w]+(?:[\w\[\]\.]+)?)'    # Object name (variable or array element or member)
            r'\s*\.\s*'                          # Dot operator with optional whitespace
            r'(?P<method>[\w]+)'                 # Method name
            r'\s*\('                             # Opening parenthesis with optional whitespace
            r'(?P<args>[^;]*?)'                  # Arguments (non-greedy)
            r'\)',                               # Closing parenthesis
            re.DOTALL
        )
        
        # Pattern to detect function calls within other function calls
        self.nested_call_pattern = re.compile(
            r'(?P<outer_func>[\w:~]+)'           # Outer function name
            r'\s*\(\s*'                          # Opening parenthesis with optional whitespace
            r'(?:[^()]|\([^()]*\))*?'            # Any content that's not parentheses, or balanced parentheses
            r'(?P<inner_func>[\w:~]+)'           # Inner function name
            r'\s*\('                             # Inner function opening parenthesis
            r'(?P<args>[^;]*?)'                  # Inner function arguments (non-greedy)
            r'\)'                                # Inner function closing parenthesis
            r'(?:[^()]|\([^()]*\))*?'            # Any remaining content
            r'\)',                               # Outer function closing parenthesis
            re.DOTALL
        )
        
        # Pattern to detect function calls within if conditions
        self.conditional_call_pattern = re.compile(
            r'if\s*\(\s*'                        # if statement with opening parenthesis
            r'(?P<condition>[^)]*?)'              # Condition part (non-greedy)
            r'\)\s*'                             # Closing parenthesis with optional whitespace
            r'(?P<then>\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}[^{}]*)*\})',  # Then block with nested braces
            re.DOTALL
        )
        
        self.class_pattern = re.compile(
            r'class\s+(?P<name>\w+)(?:\s*:\s*(?:public|protected|private)\s+(?P<parent>\w+))?\s*'  # Class name
            r'\{(?P<body>.*?)\};',
            re.DOTALL
        )
        
        self.namespace_pattern = re.compile(
            r'namespace\s+(?P<name>\w+)\s*\{(?P<body>.*?)\}',  # Namespace name
            re.DOTALL
        )
        
        # Pattern for if conditions
        self.if_condition_pattern = re.compile(
            r'if\s*\(\s*(?P<condition>[^)]+)\s*\)\s*(?P<then>\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}[^{}]*)*\})',
            re.DOTALL
        )

    def _debug_print(self, message: str):
        """Print a debug message if debug mode is enabled."""
        if self.debug_mode:
            print(f"[DEBUG] {message}")

    def _is_local_file(self, file_path: str) -> bool:
        """Check if a file is within the source directories."""
        if not self.local_only:
            return True
        
        file_path = os.path.abspath(file_path)
        for src_dir in self.src_directories:
            src_dir = os.path.abspath(src_dir)
            if file_path.startswith(src_dir):
                return True
        
        return False

    def _is_local_function(self, func_name: str) -> bool:
        """
        Check if a function is likely a local function (defined in our codebase)
        rather than from an external library or standard C/C++ function.
        
        Args:
            func_name: The name of the function to check
            
        Returns:
            True if the function is likely locally defined, False otherwise
        """
        # Common external library function prefixes
        external_prefixes = [
            'MPI_', 'hip', 'Hip', 'HIP', 'cuda', 'Cuda', 'CUDA', 
            'GPTL', 'nc_', 'NC_', 'TIMER', 'CHECK', '__', 
            'gl', 'GL_', 'cl', 'CL_', 'dl', 'DL_', 
            'SDL_', 'GLFW', 'Py', 'py', 'PY_', 'cv', 'CV_',
            'boost::', 'std::', 'thrust::', 'tbb::', 'llvm::',
            'eigen::', 'tf::', 'torch::', 'cblas_'
        ]
        
        # Common standard library and external functions
        common_external_functions = [
            # C standard library
            'printf', 'sprintf', 'fprintf', 'snprintf', 'scanf', 'fscanf', 'sscanf',
            'malloc', 'calloc', 'realloc', 'free', 'alloc', 'dealloc',
            'memcpy', 'memmove', 'memset', 'memcmp', 'memchr',
            'strcpy', 'strncpy', 'strcat', 'strncat', 'strcmp', 'strncmp', 'strlen',
            'atoi', 'atof', 'atol', 'strtol', 'strtoul', 'strtod',
            'fopen', 'fclose', 'fread', 'fwrite', 'fseek', 'ftell', 'fflush',
            'exit', 'abort', 'assert', 'getenv', 'system',
            'qsort', 'bsearch', 'rand', 'srand',
            'time', 'clock', 'difftime', 'ctime', 'localtime', 'gmtime', 'mktime',
            'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'atan2',
            'sinh', 'cosh', 'tanh', 'exp', 'log', 'log10', 'pow', 'sqrt', 'ceil', 'floor', 'fabs',
            'setjmp', 'longjmp', 'signal', 'raise',
            
            # C++ standard library (partial)
            'new', 'delete', 'operator', 'make_shared', 'make_unique',
            'begin', 'end', 'cbegin', 'cend', 'rbegin', 'rend',
            'resize', 'reserve', 'shrink_to_fit', 'clear', 'empty', 'size', 'push_back', 'pop_back',
            'insert', 'erase', 'find', 'count', 'lower_bound', 'upper_bound',
            'sort', 'stable_sort', 'merge', 'accumulate', 'fill', 'copy', 'transform',
            'emplace', 'emplace_back', 'swap', 'move',
            
            # CUDA/HIP specific
            'launch', 'barrier', 'threadIdx', 'blockIdx', 'gridDim', 'blockDim',
            'kernel', 'global', 'device', 'host', 'shared', 'atomicAdd', 'atomicSub', 'atomicMin', 'atomicMax',
            'syncthreads', 'syncwarp', 'threadfence', 'threadfence_block', 'threadfence_system'
        ]
        
        # Macro pattern characteristics
        macro_patterns = {
            'prefixes': ['DEFINE_', 'DECLARE_', 'IMPLEMENT_', 'MACRO_', 'CREATE_'],
            'suffixes': ['_MACRO', '_DEFINE', '_DECL', '_IMPL'],
            'all_uppercase': True,  # If a name is ALL_UPPERCASE, it's likely a macro
            'all_uppercase_with_underscores': True,  # If a name is ALL_UPPERCASE_WITH_UNDERSCORES, likely a macro
            'numeric_suffixes': False  # Names ending with numbers might be macros like INDEX1, INDEX2
        }
        
        # List of functions that should always be treated as local
        # These are known user functions that shouldn't be filtered out
        always_local_functions = [
            'cu_glob_updatehalo_p', 'svrasr', 'ILU_5', 'psolve_main', 'mygetVar_rand',
            'print_norm', 'catch', 'min', 'gen', 'dis',
            'reduction', 'schedule', 'DURATION_ms'
        ]
        
        # Check if the function is in our "always local" list
        if func_name in always_local_functions:
            return True
        
        # Check if the function is in our known functions list (directly or as a part of a scoped name)
        for full_name in self.functions.keys():
            if (full_name == func_name or 
                full_name.endswith(f"::{func_name}") or 
                full_name.split('::')[-1] == func_name):
                return True
        
        # Analyze if the function name follows macro patterns
        def is_likely_macro(name):
            # Check for common macro prefixes and suffixes
            for prefix in macro_patterns['prefixes']:
                if name.startswith(prefix):
                    return True
            
            for suffix in macro_patterns['suffixes']:
                if name.endswith(suffix):
                    return True
            
            # Check if it's all uppercase with underscores (common for macros)
            if macro_patterns['all_uppercase'] and name.isupper() and len(name) > 3:
                return True
            
            if macro_patterns['all_uppercase_with_underscores'] and '_' in name and name.isupper():
                return True
            
            # GPU/HPC specific macro naming patterns
            
            # Common indexing patterns like index_b, index3, index4b, index_x
            if name.startswith('index_') or (name.startswith('index') and any(c.isdigit() for c in name)):
                return True
                
            # CUDA/HPC parameter macros like cupara_jend, cupara_jte, cupara_jte1
            if name.startswith('cupara_'):
                return True
                
            # Thread/block configuration macros
            if name.startswith('threadsPerBlock') or name.startswith('numBlocks'):
                return True
            
            # Basic indexing names for CUDA/HIP programming
            if ('block' in name.lower() and 'thread' in name.lower()):
                return True
                
            if name.startswith('__') and name.endswith('__'):
                return True
                
            # Common GPU indexing macro patterns
            indexing_patterns = ['idx', 'idy', 'idz', 'tid', 'bid', 'gid', 'lid']
            if any(pattern == name.lower() for pattern in indexing_patterns):
                return True
            
            # Check for mixed case with numbers (like index3D, grid2D, etc.)
            if (any(c.isdigit() for c in name) and 
                any(c.isupper() for c in name) and 
                any(word in name.lower() for word in ['index', 'grid', 'block', 'thread', 'dim', 'size'])):
                return True
            
            return False
        
        # If it looks like a macro but is not in our known function list, filter it out
        if is_likely_macro(func_name) and func_name not in always_local_functions:
            return False
                
        # Check against external prefixes
        for prefix in external_prefixes:
            if func_name.startswith(prefix):
                return False
                
        # Check against common external functions
        for func in common_external_functions:
            if func_name == func or func_name.startswith(f"{func}_"):
                return False
        
        # For functions that don't match our known local functions but also don't match 
        # our external function patterns, we'll be conservative and check the naming pattern
        
        # Single letter names like i, x, etc. are likely local variables mistaken as function calls
        if len(func_name) == 1:
            return False
            
        # If function name contains numbers, it's more likely to be a local function
        # (many library functions don't include numbers) unless it matches a macro pattern
        if any(c.isdigit() for c in func_name) and not is_likely_macro(func_name):
            return True
            
        # If lowercase with underscores or pascal case, more likely local unless caught by earlier checks
        if '_' in func_name or (func_name[0].islower() and any(c.isupper() for c in func_name[1:])):
            return True
            
        # Default to assuming it might be local if we've gotten this far
        return True

    def parse_all_files(self, include_pattern=None, exclude_pattern=None):
        """
        Parse all C/C++ files in the source directories.
        
        Args:
            include_pattern: Optional regex pattern for files to include
            exclude_pattern: Optional regex pattern for files to exclude
        """
        all_files = []
        
        # Collect all C/C++ files
        for src_dir in self.src_directories:
            self._debug_print(f"Scanning directory: {src_dir}")
            
            if self.is_recursive:
                for root, _, files in os.walk(src_dir):
                    for file in files:
                        if file.endswith(('.c', '.cpp', '.cxx', '.cc', '.h', '.hpp')):
                            file_path = os.path.join(root, file)
                            # Apply include/exclude filters if specified
                            if include_pattern and not re.search(include_pattern, file_path):
                                continue
                            if exclude_pattern and re.search(exclude_pattern, file_path):
                                continue
                            all_files.append(file_path)
            else:
                # Non-recursive scan
                files = [os.path.join(src_dir, f) for f in os.listdir(src_dir) 
                         if os.path.isfile(os.path.join(src_dir, f)) and 
                         f.endswith(('.c', '.cpp', '.cxx', '.cc', '.h', '.hpp'))]
                
                # Apply include/exclude filters
                if include_pattern:
                    files = [f for f in files if re.search(include_pattern, f)]
                if exclude_pattern:
                    files = [f for f in files if not re.search(exclude_pattern, f)]
                
                all_files.extend(files)
        
        self._debug_print(f"Found {len(all_files)} C/C++ files to parse")
        
        # Parse each file to extract functions
        for file_path in all_files:
            self._debug_print(f"Parsing file: {file_path}")
            
            try:
                with open(file_path, 'r', encoding='utf-8', errors='ignore') as file:
                    content = file.read()
                    
                    # Extract the compilation unit name (file basename without extension)
                    unit_name = os.path.basename(file_path)
                    
                    # Try several approaches to get just the filename without extension
                    if '.' in unit_name:
                        unit_name = unit_name[:unit_name.rfind('.')]
                    
                    # Initialize compilation unit if not already present
                    if unit_name not in self.compilation_units:
                        self.compilation_units[unit_name] = []
                    
                    # Parse the file content to find functions
                    self._parse_file(content, unit_name, file_path)
                    
                    # Special handling for test files
                    if 'test' in unit_name.lower():
                        self._parse_test_file(content, unit_name, file_path)
                        
            except Exception as e:
                self._debug_print(f"Error parsing file {file_path}: {e}")
        
        # After parsing all files, build the call graph
        self._build_call_graph()
        
        self._debug_print(f"Parsing complete. Found {len(self.functions)} functions in {len(self.compilation_units)} compilation units")
        
    def _parse_test_file(self, content: str, unit_name: str, file_path: str):
        """Special parsing for test files to better extract test functions and their relationships."""
        # Generate a special test entry point name for this file
        test_main_name = f"{unit_name}_main"
        
        # Check if we already have a main function for this unit
        has_main = False
        for func_name in self.compilation_units.get(unit_name, []):
            if func_name == "main" or func_name.endswith("::main"):
                has_main = True
                # Create synthetic test_main node if it doesn't exist
                if test_main_name not in self.functions:
                    test_main = FunctionNode(test_main_name)
                    self.functions[test_main_name] = test_main
                    # Don't connect test_main to the actual main to avoid showing main as a subfunction
                    # main_func = self.functions[func_name]
                    # test_main.add_call(main_func)
                    
                    # Register in compilation unit
                    if test_main_name not in self.compilation_units[unit_name]:
                        self.compilation_units[unit_name].append(test_main_name)
                        
                    # Add as entry point
                    if test_main_name not in self.entry_points:
                        self.entry_points.append(test_main_name)
                
        # Look for test_* functions in the file
        test_pattern = re.compile(r'\b(void|int|bool|double|float)\s+(test_\w+)\s*\([^)]*\)\s*\{', re.MULTILINE)
        for match in test_pattern.finditer(content):
            test_func_name = match.group(2)
            
            # Check if this function already exists
            if test_func_name in self.functions:
                # The function was already parsed in the regular parse phase
                if test_main_name in self.functions and test_func_name != test_main_name:
                    # Connect to test_main if exists
                    test_main = self.functions[test_main_name]
                    test_func = self.functions[test_func_name]
                    test_main.add_call(test_func)
            else:
                # Create a new function node
                test_func = FunctionNode(test_func_name)
                self.functions[test_func_name] = test_func
                
                # Add to compilation unit
                if test_func_name not in self.compilation_units[unit_name]:
                    self.compilation_units[unit_name].append(test_func_name)
                
                # Connect to test_main if exists
                if test_main_name in self.functions:
                    test_main = self.functions[test_main_name]
                    test_main.add_call(test_func)
        
        # Look for compare_* functions which are common in testing code
        compare_pattern = re.compile(r'\b(void|int|bool|double|float)\s+(compare_\w+)\s*\([^)]*\)\s*\{', re.MULTILINE)
        for match in compare_pattern.finditer(content):
            compare_func_name = match.group(2)
            
            # Check if this function already exists
            if compare_func_name in self.functions:
                # Function was already parsed
                if test_main_name in self.functions:
                    # Connect to test_main if exists
                    test_main = self.functions[test_main_name]
                    compare_func = self.functions[compare_func_name]
                    test_main.add_call(compare_func)
            else:
                # Create a new function node
                compare_func = FunctionNode(compare_func_name)
                self.functions[compare_func_name] = compare_func
                
                # Add to compilation unit
                if compare_func_name not in self.compilation_units[unit_name]:
                    self.compilation_units[unit_name].append(compare_func_name)
                
                # Connect to test_main if exists
                if test_main_name in self.functions:
                    test_main = self.functions[test_main_name]
                    test_main.add_call(compare_func)
                    
        # Find compare_* functions in the file and analyze their content to detect method calls/references
        for func_name in self.compilation_units.get(unit_name, []):
            if func_name.startswith("compare_"):
                # Get the function object
                func = self.functions.get(func_name)
                if func and func.source_code:
                    # Extract function body
                    body_match = re.search(r'\{(.*)\}', func.source_code, re.DOTALL)
                    if body_match:
                        func_body = body_match.group(1)
                        
                        # Find all constant identifiers and enum values that could represent methods
                        # This looks for words that might be method references in a compare_methods function
                        identifiers = set(re.findall(r'\b([A-Z][A-Z0-9_]*)\b', func_body))
                        
                        # Also find quoted strings that could be method names
                        quoted_strings = re.findall(r'"([^"]+)"', func_body)
                        for quoted in quoted_strings:
                            # Only add if it looks like a method name (uppercase or has specific patterns)
                            if quoted.isupper() or '_' in quoted:
                                identifiers.add(quoted)
                        
                        # Create nodes for these identifiers and connect them
                        for identifier in identifiers:
                            # Skip common C++ constants and keywords
                            if identifier in ('NULL', 'TRUE', 'FALSE', 'nullptr', 'SIZE', 'MAX', 'MIN'):
                                continue
                                
                            # Create node if it doesn't exist
                            if identifier not in self.functions:
                                self._debug_print(f"Creating node for method identifier found in {func_name}: {identifier}")
                                method_func = FunctionNode(identifier)
                                self.functions[identifier] = method_func
                            else:
                                method_func = self.functions[identifier]
                            
                            # Connect the compare_method to this method
                            func.add_call(method_func)

    def _parse_file(self, content: str, unit_name: str, file_path: str):
        """Parse a C/C++ file to extract functions, classes, and namespaces."""
        if not self._is_local_file(file_path):
            self._debug_print(f"Skipping non-local file: {file_path}")
            return
            
        # Get the base name of the file (without path or extension) to use as a namespace
        file_base = os.path.splitext(os.path.basename(file_path))[0]
        
        # Initialize entry in compilation units dictionary
        if file_base not in self.compilation_units:
            self.compilation_units[file_base] = []
        
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
        except Exception as e:
            self._debug_print(f"Error reading file {file_path}: {e}")
            return
            
        # Remove comments to simplify parsing
        content = self._remove_comments(content)
        
        # Special handling for main function detection
        self._find_main_function(content, file_base)
        
        # Special handling for test_ functions detection
        self._find_test_functions(content, file_base)
        
        # Handle namespaces
        namespaces = self._parse_namespaces(content)
        for namespace, namespace_content in namespaces:
            # Parse classes within namespace
            self._parse_classes(namespace_content, file_base, namespace)
            
            # Parse functions within namespace
            self._parse_functions(namespace_content, file_base, namespace)
        
        # Parse top-level functions and classes (outside any namespace)
        self._parse_classes(content, file_base)
        self._parse_functions(content, file_base)

    def _remove_comments(self, content: str) -> str:
        """Remove C and C++ style comments from the code."""
        # Remove C-style multi-line comments /* ... */
        content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
        
        # Remove C++-style single-line comments // ...
        content = re.sub(r'//.*?$', '', content, flags=re.MULTILINE)
        
        return content

    def _parse_namespaces(self, content: str) -> List[Tuple[str, str]]:
        """Parse namespace declarations and their content."""
        namespaces = []
        for match in self.namespace_pattern.finditer(content):
            namespace_name = match.group('name')
            namespace_body = match.group('body')
            namespaces.append((namespace_name, namespace_body))
        
        return namespaces

    def _parse_classes(self, content: str, file_base: str, namespace: str = ''):
        """Parse class declarations and their methods."""
        for match in self.class_pattern.finditer(content):
            class_name = match.group('name')
            class_body = match.group('body')
            
            # Parse methods within the class
            self._parse_functions(class_body, file_base, namespace, class_name)

    def _parse_functions(self, content: str, file_base: str, namespace: str = '', class_name: Optional[str] = None):
        """Parse function definitions."""
        self._debug_print(f"Parsing functions in {file_base}, content length: {len(content)}")
        matches = list(self.function_def_pattern.finditer(content))
        self._debug_print(f"Found {len(matches)} function matches")
        
        for match in matches:
            try:
                self._debug_print(f"Groups in match: {match.groupdict().keys()}")
                func_name = match.group('name')
                func_body = match.group('body')
                self._debug_print(f"Extracted function name: {func_name}")
                
                # Skip constructor initializer lists false positives
                if ':' in func_name and not '::' in func_name:
                    continue
                
                # Handle scoped names (e.g., ClassName::method)
                if '::' in func_name:
                    parts = func_name.split('::')
                    func_name = parts[-1]
                    
                    # If we have a class scope, use it instead of the current class
                    if len(parts) > 1:
                        class_name = parts[-2]
                
                # Create a function node
                func_node = FunctionNode(func_name, namespace, class_name)
                func_node.source_code = match.group(0)
                
                # Add to function dictionary if not already there
                if func_node.full_name not in self.functions:
                    self.functions[func_node.full_name] = func_node
                    
                    # Add to compilation unit
                    self.compilation_units[file_base].append(func_node.full_name)
                
                self._debug_print(f"Found function: {func_node.full_name}")
            except Exception as e:
                self._debug_print(f"Error processing function match: {e}")
                self._debug_print(f"Match content: {match.group(0)[:100]}...")

    def resolve_calls(self, file_path: str):
        """
        Resolve function calls in a C/C++ file.
        
        Args:
            file_path: Path to the C/C++ file
        """
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
        except Exception as e:
            self._debug_print(f"Error reading file {file_path}: {e}")
            return
        
        # Remove comments
        content = self._remove_comments(content)
        
        # For each function definition in the file
        for match in self.function_def_pattern.finditer(content):
            func_name = match.group('name')
            func_body = match.group('body')
            
            # Handle scoped names
            if '::' in func_name:
                parts = func_name.split('::')
                func_name = parts[-1]
                if len(parts) > 1:
                    class_name = parts[-2]
                    
                    # Find the namespace part if any
                    namespace = '::'.join(parts[:-2]) if len(parts) > 2 else ''
                    
                    full_name = f"{namespace}::{class_name}::{func_name}" if namespace else f"{class_name}::{func_name}"
                else:
                    full_name = func_name
            else:
                # Look for the function in our dictionary
                for potential_name in self.functions.keys():
                    if potential_name.endswith(f"::{func_name}") or potential_name == func_name:
                        full_name = potential_name
                        break
                else:
                    # If not found, just use the function name
                    full_name = func_name
            
            # Skip if we can't find the caller function
            if full_name not in self.functions:
                self._debug_print(f"Cannot find caller function: {full_name}")
                continue
                
            caller = self.functions[full_name]
            
            # Parse function calls within the function body
            self._parse_function_calls(func_body, caller)

    def _parse_function_calls(self, func_body: str, caller: FunctionNode):
        """Parse function calls within a function body."""
        # Track all detected functions to avoid duplicates
        detected_calls = set()
        
        # 1. Extract direct function calls
        for match in self.function_call_pattern.finditer(func_body):
            callee_name = match.group('n')
            
            # Skip if it looks like a control structure (e.g., if, for, while)
            if callee_name in ('if', 'for', 'while', 'switch', 'catch'):
                continue
            
            # Skip standard C library functions if we're focusing on local code
            if self.local_only and self._is_standard_or_ignored(callee_name):
                continue
            
            # Special handling for hipLaunchKernelGGL
            if callee_name == 'hipLaunchKernelGGL':
                # Try to extract the actual kernel function name from the parameters
                call_full = match.group(0)  # Get the full function call text
                args = match.group('args') if 'args' in match.groupdict() else ''
                
                # Extract the first parameter of hipLaunchKernelGGL which should be the kernel function
                kernel_match = re.search(r'hipLaunchKernelGGL\s*\(\s*([A-Za-z0-9_:]+)', call_full)
                if kernel_match:
                    kernel_name = kernel_match.group(1)
                    self._debug_print(f"Found kernel function {kernel_name} through hipLaunchKernelGGL")
                    self._resolve_function_call(kernel_name, caller)
                    detected_calls.add(kernel_name)
                    continue
            
            # Try to resolve the called function
            if self._resolve_function_call(callee_name, caller):
                detected_calls.add(callee_name)
                
            # Look for function calls in arguments
            args = match.group('args') if 'args' in match.groupdict() else ''
            self._parse_function_calls_in_args(args, caller, detected_calls)
        
        # 2. Extract method calls on objects
        for match in self.method_call_pattern.finditer(func_body):
            obj_name = match.group('obj')
            method_name = match.group('method')
            
            # Skip standard library method calls if we're focusing on local code
            if self.local_only and self._is_standard_or_ignored(method_name):
                continue
                
            # Try to resolve method calls
            # For method calls, we need to check if the method belongs to a class we know
            if self._resolve_function_call(method_name, caller):
                detected_calls.add(method_name)
                
            # Also check for the fully qualified method name (Class::method)
            # This is a heuristic to handle method calls on objects of known classes
            for func_name in self.functions:
                if func_name.endswith(f"::{method_name}"):
                    callee = self.functions[func_name]
                    caller.add_call(callee)
                    detected_calls.add(func_name)
                    break
                    
            # Look for function calls in method arguments
            args = match.group('args') if 'args' in match.groupdict() else ''
            self._parse_function_calls_in_args(args, caller, detected_calls)
            
        # 3. Extract nested function calls
        for match in self.nested_call_pattern.finditer(func_body):
            outer_func = match.group('outer_func')
            inner_func = match.group('inner_func')
            
            # Skip if either looks like a control structure
            if outer_func in ('if', 'for', 'while', 'switch', 'catch') or inner_func in ('if', 'for', 'while', 'switch', 'catch'):
                continue
                
            # Skip standard library functions if focusing on local code
            if self.local_only:
                if not self._is_standard_or_ignored(outer_func):
                    if self._resolve_function_call(outer_func, caller):
                        detected_calls.add(outer_func)
                
                if not self._is_standard_or_ignored(inner_func):
                    if self._resolve_function_call(inner_func, caller):
                        detected_calls.add(inner_func)
            else:
                # If we're including all calls, resolve both functions
                self._resolve_function_call(outer_func, caller)
                self._resolve_function_call(inner_func, caller)
                detected_calls.add(outer_func)
                detected_calls.add(inner_func)
                
            # Also process the arguments for more potential function calls
            args = match.group('args') if 'args' in match.groupdict() else ''
            self._parse_function_calls_in_args(args, caller, detected_calls)
            
        # 4. Extract function calls in conditional statements
        for match in self.conditional_call_pattern.finditer(func_body):
            condition = match.group('condition')
            then_block = match.group('then')
            
            # Parse condition for function calls
            self._parse_function_calls_in_args(condition, caller, detected_calls)
            
            # Parse then block for function calls (recursively)
            # This is a simplified approach; we're not tracking the condition for each call
            self._parse_function_calls(then_block, caller)
            
        # Find else blocks as well
        else_pattern = re.compile(r'else\s*\{([^{}]*(?:\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}[^{}]*)*)\}', re.DOTALL)
        for match in else_pattern.finditer(func_body):
            else_block = match.group(1)
            
            # Parse else block for function calls
            self._parse_function_calls(else_block, caller)
        
        # Debugging: Print information about the calls after parsing
        if self.debug_mode and caller.calls:
            self._debug_print(f"Found {len(caller.calls)} function calls from {caller.full_name}")
            for called_func in caller.calls:
                self._debug_print(f"  - {caller.full_name} calls {called_func.full_name}")
        elif self.debug_mode:
            self._debug_print(f"No function calls found from {caller.full_name}")
            
    def _parse_function_calls_in_args(self, args_text: str, caller: FunctionNode, detected_calls: set):
        """Parse function calls within function arguments."""
        # Check for function calls within arguments
        for match in self.function_call_pattern.finditer(args_text):
            callee_name = match.group('n')
            
            # Skip control structures and standard functions
            if callee_name in ('if', 'for', 'while', 'switch', 'catch'):
                continue
                
            if self.local_only and self._is_standard_or_ignored(callee_name):
                continue
                
            # Try to resolve the function call
            if self._resolve_function_call(callee_name, caller):
                detected_calls.add(callee_name)
                
            # Recursive check for nested arguments
            if 'args' in match.groupdict():
                nested_args = match.group('args')
                self._parse_function_calls_in_args(nested_args, caller, detected_calls)

    def _resolve_function_call(self, callee_name: str, caller: FunctionNode, condition: Optional[str] = None):
        """
        Resolve a function call to a FunctionNode.
        
        Args:
            callee_name: Name of the called function
            caller: FunctionNode representing the calling function
            condition: Optional condition under which the function is called
        """
        # Special handling for test_ functions - always include them
        if callee_name.startswith('test_') or 'test_' in callee_name:
            # Check if this test function already exists
            if callee_name in self.functions:
                callee = self.functions[callee_name]
                caller.add_call(callee, condition)
                return True
            else:
                # Create a placeholder for the test function
                callee = FunctionNode(callee_name)
                self.functions[callee_name] = callee
                caller.add_call(callee, condition)
                return True
                
        # Special handling for potentially important function patterns 
        important_patterns = ['decompose_grid', 'communicate_', 'print_']
        if any(pattern in callee_name for pattern in important_patterns):
            # Check if this function already exists
            if callee_name in self.functions:
                callee = self.functions[callee_name]
                caller.add_call(callee, condition)
                return True
            else:
                # Create a placeholder
                callee = FunctionNode(callee_name)
                self.functions[callee_name] = callee
                caller.add_call(callee, condition)
                return True
                
        # If it's a qualified name with ::, try to match it directly
        if '::' in callee_name:
            if callee_name in self.functions:
                callee = self.functions[callee_name]
                caller.add_call(callee, condition)
                return True
            
            # Try variations of the qualified name
            parts = callee_name.split('::')
            base_name = parts[-1]
            
            for func_name in self.functions:
                if func_name.endswith(f"::{base_name}"):
                    callee = self.functions[func_name]
                    caller.add_call(callee, condition)
                    return True
        
        # For simple names, try to find in same scope first
        caller_scope = caller.full_name.rsplit('::', 1)[0] if '::' in caller.full_name else ''
        
        if caller_scope:
            scoped_name = f"{caller_scope}::{callee_name}"
            if scoped_name in self.functions:
                callee = self.functions[scoped_name]
                caller.add_call(callee, condition)
                return True
        
        # Try just the function name
        if callee_name in self.functions:
            callee = self.functions[callee_name]
            caller.add_call(callee, condition)
            return True
        
        # Check if it's a function name in any scope/namespace
        for func_name in self.functions:
            if func_name.endswith(f"::{callee_name}"):
                callee = self.functions[func_name]
                caller.add_call(callee, condition)
                return True
                
        # Add forward declarations for unresolved functions found in the code
        if callee_name not in self.functions and not self._is_standard_or_ignored(callee_name):
            # Create a placeholder for the unresolved function
            self._debug_print(f"Added forward declaration: {callee_name} in {caller.full_name}")
            callee = FunctionNode(callee_name)
            self.functions[callee_name] = callee
            caller.add_call(callee, condition)
            return True
            
        return False
        
    def _is_standard_or_ignored(self, func_name: str) -> bool:
        """Check if a function name is a standard library function or should be ignored."""
        # Always include test_ functions regardless of other filters
        if "test_" in func_name.lower():
            return False
            
        # List of common C/C++ standard library functions and keywords to ignore
        standard_funcs = {
            # C/C++ standard library functions
            'printf', 'scanf', 'malloc', 'free', 'realloc', 'calloc',
            'strlen', 'strcpy', 'strncpy', 'strcmp', 'strncmp',
            'memcpy', 'memset', 'memmove', 'memcmp',
            'fopen', 'fclose', 'fread', 'fwrite', 'fprintf',
            'new', 'delete', 'sizeof', 'typeof',
            'assert', 'exit', 'abort',
            
            # GPTL timing functions
            'GPTLstart', 'GPTLstop', 'GPTLsetoption', 'GPTLinitialize', 'GPTLfinalize',
            'TIMERSTART', 'TIMEREND', 'DURATION_ms',
            
            # CUDA/HIP block configuration
            'threadsPerBlock', 'threadsPerBlock1', 'threadsPerBlock2',
            'numBlocks', 'numBlocks1', 'numBlocks2',
            
            # Common math functions
            'min', 'max', 'sqrt', 'ceil', 'floor', 'abs', 'pow', 'exp', 'log',
            'gen', 'dis', 'rand', 'random', 'srand',  # Random number generation functions
            
            # GPU synchronization
            'hipDeviceSynchronize', 'cudaDeviceSynchronize', 'hipblasSetPointerMode',
            'hipSetDevice', 'hipblasCreate',
            
            # Other common functions to filter
            'CHECK', 'svrasr', 'MPI_Comm_free', 'MPI_Finalize', 'MPI_Comm_size',
            'MPI_Init', 'MPI_Comm_split', 'MPI_Allreduce', 'hipblasSdot',
            
            # Additional functions to filter
            'std::string', 'std::to_string', 'string', 'to_string',
            'nprocx', 'parse_args', 'ijk_index',
            
            # Micro functions for index calculation
            'index3', 'index4', 'index_x', 'index4b', 'index_b', 'initialize_ijk_index', 'int',
            'cupara_jte', 'cupara_jend', 'cupara_jte1'
        }
        
        # Do not filter functions named with test_ prefix - these are usually important test functions
        if func_name.startswith('test_'):
            return False
            
        # Check if the function name contains patterns we want to ignore
        ignored_patterns = [
            'GPTLstop', 'GPTLstart', 'GPTL', 'TIMER',  # GPTL timing functions
            'Device', 'Synchronize',                    # Device synchronization
            'threads', 'Blocks', 'Block',               # CUDA/HIP block configuration
            'std::',                                    # C++ standard library
            'index',                                    # Index calculation functions
            'cupara_',                                  # CUDA/HIP parameters
            'mt19937'                                   # Random number generator
        ]
        
        # Do not ignore functions with test_ pattern - these are often test functions
        for pattern in ignored_patterns:
            if pattern.lower() in func_name.lower() and not 'test_' in func_name.lower():
                return True
                
        # Return True if the function name is in our list of standard functions
        return func_name in standard_funcs

    def _find_main_function(self, content: str, file_base: str):
        """Find the main function in a C/C++ file."""
        # First try the detailed main function pattern
        for match in self.main_function_pattern.finditer(content):
            # Found main function, extract its body
            body = match.group('body')
            
            # Create a function node for main
            main_func = FunctionNode("main")
            main_func.source_code = match.group(0)
            
            # Generate a unique key for this main function
            main_key = f"{file_base}_main" if file_base != "main" else "main"
            
            # Add main to functions dictionary
            # Keep the original "main" key for backwards compatibility
            self.functions["main"] = main_func
            
            # Also store under the unique file-specific key
            if main_key != "main":
                self.functions[main_key] = main_func
            
            # Add to entry points
            self.entry_points.append("main")
            if main_key != "main":
                self.entry_points.append(main_key)
            
            # Add the main function to this file's compilation unit - both "main" and file-specific key
            self.compilation_units[file_base].append("main")
            if main_key != "main":
                self.compilation_units[file_base].append(main_key)
            
            self._debug_print(f"Found main function in {file_base}")
            
            # Extract function calls from main
            self._extract_calls_from_main(body, main_func)
            
            return True
        
        # Try a more basic pattern for general main function detection
        # This is a more permissive pattern that should catch a wider range of main function declarations
        basic_main_pattern = re.compile(
            r'int\s+main\s*\([^)]*\)\s*\{',
            re.DOTALL
        )
        
        if basic_main_pattern.search(content):
            self._debug_print(f"Found main function with basic pattern in {file_base}")
            
            # Try to extract the function body
            start_idx = content.find("int main")
            if start_idx >= 0:
                # Find the opening brace
                brace_idx = content.find('{', start_idx)
                if brace_idx >= 0:
                    # Extract the function body by matching braces
                    depth = 1
                    body_start = brace_idx + 1
                    body_end = body_start
                    
                    for i in range(body_start, len(content)):
                        if content[i] == '{':
                            depth += 1
                        elif content[i] == '}':
                            depth -= 1
                            if depth == 0:
                                body_end = i
                                break
                    
                    body = content[body_start:body_end]
                    
                    # Create a function node for main
                    main_func = FunctionNode("main")
                    main_func.source_code = content[start_idx:body_end+1]
                    
                    # Generate a unique key for this main function
                    main_key = f"{file_base}_main" if file_base != "main" else "main"
                    
                    # Add main to functions dictionary
                    # Keep the original "main" key for backwards compatibility
                    self.functions["main"] = main_func
                    
                    # Also store under the unique file-specific key
                    if main_key != "main":
                        self.functions[main_key] = main_func
                    
                    # Add to entry points
                    self.entry_points.append("main")
                    if main_key != "main":
                        self.entry_points.append(main_key)
                    
                    # Add the main function to this file's compilation unit - both "main" and file-specific key
                    self.compilation_units[file_base].append("main")
                    if main_key != "main":
                        self.compilation_units[file_base].append(main_key)
                    
                    self._debug_print(f"Found main function: main in {file_base}")
                    
                    # Extract function calls from main
                    self._extract_calls_from_main(body, main_func)
                    
                    return True
            
        return False
        
    def _extract_calls_from_main(self, body: str, main_func: FunctionNode):
        """Extract function calls from the main function body."""
        # Look for known function names in the main body
        common_functions = ["h1_initialize", "h1_finalize", "mygetvar_c_", "assign_indices", 
                           "display_help", "cu_solve_helmholts_", "getijk_", "grapes_abort_"]
        
        for func_name in common_functions:
            # Simple pattern to match function calls
            pattern = r'(?<![a-zA-Z0-9_])' + re.escape(func_name) + r'\s*\('
            if re.search(pattern, body):
                # If the function exists in our dictionary
                if func_name in self.functions:
                    callee = self.functions[func_name]
                    main_func.add_call(callee)
                    self._debug_print(f"Found direct call in main: {func_name}")
                else:
                    # Create a placeholder
                    callee = FunctionNode(func_name)
                    self.functions[func_name] = callee
                    main_func.add_call(callee)
                    self._debug_print(f"Added function called from main: {func_name}")
        
        # Now also use the regular function call pattern
        for match in self.function_call_pattern.finditer(body):
            callee_name = match.group('n')
            
            # Skip common C/C++ keywords and control structures
            if callee_name in ('if', 'for', 'while', 'switch', 'return', 'sizeof', 'printf'):
                continue
                
            # Try to resolve the function call
            self._resolve_function_call(callee_name, main_func)

    def _find_entry_points(self):
        """Find entry points in the code."""
        self._debug_print("Identifying entry points...")
        
        # Clear existing entry points
        self.entry_points = []
        
        # Look for main functions first
        for func_name in self.functions:
            if func_name == "main" or func_name.endswith("::main") or func_name.endswith("_main"):
                self.entry_points.append(func_name)
                self._debug_print(f"Found entry point: {func_name}")
                
        # If no main functions found, look for test functions
        if not self.entry_points:
            for func_name in self.functions:
                if func_name.startswith("test_") or "test" in func_name.lower():
                    self.entry_points.append(func_name)
                    self._debug_print(f"Found test entry point: {func_name}")
                    
        # If still no entry points, use functions that aren't called by anything else
        if not self.entry_points:
            for func_name, func in self.functions.items():
                if not func.called_by:
                    self.entry_points.append(func_name)
                    self._debug_print(f"Using uncalled function as entry point: {func_name}")
                    
        self._debug_print(f"Identified {len(self.entry_points)} entry points")

    def _print_call_graph_summary(self):
        """Print a summary of the call graph."""
        print("\nCall Graph Summary:")
        print(f"Total functions: {len(self.functions)}")
        print(f"Total compilation units: {len(self.compilation_units)}")
        print(f"Entry points: {self.entry_points}")
        
        # Functions that call the most other functions
        most_calls = sorted(self.functions.items(), key=lambda x: len(x[1].calls), reverse=True)
        print("\nFunctions with most outgoing calls:")
        for func_name, func_node in most_calls[:5]:
            print(f"  {func_name}: {len(func_node.calls)} calls")
        
        # Functions called by the most other functions
        most_called = sorted(self.functions.items(), key=lambda x: len(x[1].called_by), reverse=True)
        print("\nMost called functions:")
        for func_name, func_node in most_called[:5]:
            print(f"  {func_name}: called by {len(func_node.called_by)} functions")

    def _find_test_functions(self, content: str, file_base: str):
        """Find test_* functions in a C/C++ file using advanced pattern detection."""
        # Add a specific pattern for test_fdm_consistency
        test_fdm_consistency_pattern = re.compile(
            r'void\s+(test_fdm_consistency)\s*\((?:bool\s+\w+\s*=\s*false)?\)\s*\{',
            re.MULTILINE
        )
        
        # Check specifically for test_fdm_consistency
        for match in test_fdm_consistency_pattern.finditer(content):
            func_name = match.group(1)
            self._debug_print(f"Found test_fdm_consistency function with default params: {func_name}")
            
            # Create a function node
            func_node = FunctionNode(func_name)
            
            # Extract the full function text to better analyze it
            match_text = match.group(0)
            if '{' in match_text:
                # Find the end of the function by matching braces
                open_pos = match_text.find('{')
                braces = 1
                pos = open_pos + 1
                
                while pos < len(content) and braces > 0:
                    if content[pos] == '{':
                        braces += 1
                    elif content[pos] == '}':
                        braces -= 1
                    pos += 1
                    
                if braces == 0:
                    # Extract the full function body
                    full_body = content[match.start():pos]
                    func_node.source_code = full_body
                    
                    # Add to functions dictionary if not already there
                    if func_name not in self.functions:
                        self.functions[func_name] = func_node
                        
                        # Add to compilation unit
                        if file_base not in self.compilation_units:
                            self.compilation_units[file_base] = []
                        if func_name not in self.compilation_units[file_base]:
                            self.compilation_units[file_base].append(func_name)
                        
                        # Add to entry points
                        if func_name not in self.entry_points:
                            self.entry_points.append(func_name)
                        
                        # Parse the function body to extract calls
                        self._parse_function_calls(full_body, func_node)
                        
                        # Print what functions this one calls
                        if func_node.calls:
                            self._debug_print(f"test_fdm_consistency calls {len(func_node.calls)} functions:")
                            for called_func in func_node.calls:
                                self._debug_print(f"  - {called_func.full_name}")
        
        # Define more comprehensive patterns for other test functions
        test_patterns = [
            # Basic test function patterns
            r'(?P<return_type>void|int|bool)\s+(test_[a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*\{',
            
            # Test functions with namespace/class qualifier
            r'(?P<return_type>void|int|bool)\s+([a-zA-Z0-9_:]+::test_[a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*\{',
            
            # Test functions with default parameters
            r'(?P<return_type>void|int|bool)\s+(test_[a-zA-Z0-9_]+)\s*\((?:[^=]*=\s*[^,)]+[^)]*)\)\s*\{',
            
            # Test functions with complex return types (templates, pointers, etc.)
            r'(?P<return_type>[\w:<>*&]+)\s+(test_[a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*\{',
        ]
        
        # Also match any functions whose names contain test patterns
        test_name_patterns = [
            'test_fdm_', 'test_multi_', 'test_global_', 'test_tensor_', 'test_comm_'
        ]
        
        # Generic pattern to match any function definition
        generic_func_pattern = re.compile(
            r'(?P<return_type>(?:[\w:<>]+(?:\s*[*&])*\s+)+|void\s+)'  # Return type
            r'(?P<name>[\w:~]+)\s*'                    # Function name
            r'\((?P<params>[^{;]*?)\)\s*'              # Parameters
            r'(?:(?:const|noexcept|override|final)?\s*)*'  # Optional qualifiers
            r'(?P<body>\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}[^{}]*)*\})',  # Function body
            re.DOTALL
        )
        
        # Match specific test function patterns
        for pattern in test_patterns:
            matches = re.finditer(pattern, content, re.MULTILINE)
            for match in matches:
                test_func_name = match.group(2)  # Group 2 has the function name
                self._debug_print(f"Found test function using pattern: {test_func_name}")
                
                # Process the function match
                self._process_test_function_match(test_func_name, match.group(0), file_base)
        
        # Use the generic pattern to look for functions with test name patterns
        for match in generic_func_pattern.finditer(content):
            func_name = match.group('name')
            
            # Check if the function name contains any test patterns
            if any(pattern in func_name.lower() for pattern in test_name_patterns):
                self._debug_print(f"Found potential test function by name pattern: {func_name}")
                
                # Process the function match
                self._process_test_function_match(func_name, match.group(0), file_base)
                
                # Also look for specific parameter patterns that indicate test functions
                params = match.group('params')
                if '=' in params:  # Has default parameter(s)
                    self._debug_print(f"Function has default parameters: {func_name}")
                    # Extract function calls from the body
                    func_body = match.group('body')
                    
                    # Create a temporary FunctionNode for this match
                    temp_node = FunctionNode(func_name)
                    
                    # Parse the function calls in this body
                    self._parse_function_calls(func_body, temp_node)
                    
                    if temp_node.calls:
                        self._debug_print(f"Found {len(temp_node.calls)} calls in test function {func_name}")
                        
                        # If we have an existing node for this function, update its calls
                        if func_name in self.functions:
                            existing_node = self.functions[func_name]
                            for called_func in temp_node.calls:
                                existing_node.add_call(called_func)
                                
                            # Update the source code if not already present
                            if not existing_node.source_code:
                                existing_node.source_code = match.group(0)
    
    def _process_test_function_match(self, func_name: str, source_code: str, file_base: str):
        """Process a matched test function, creating a FunctionNode for it."""
        # Handle scoped names (e.g., ClassName::test_method)
        namespace = ''
        class_name = None
        
        if '::' in func_name:
            parts = func_name.split('::')
            func_name_only = parts[-1]
            
            # If we have a class scope, extract it
            if len(parts) > 1:
                class_name = parts[-2]
                
                # Extract namespace if there are more parts
                if len(parts) > 2:
                    namespace = '::'.join(parts[:-2])
        else:
            func_name_only = func_name
        
        # Create a function node
        func_node = FunctionNode(func_name_only, namespace, class_name)
        func_node.source_code = source_code
        
        # Add to functions dictionary if not already there
        if func_node.full_name not in self.functions:
            self.functions[func_node.full_name] = func_node
            
            # Add to compilation unit
            if file_base not in self.compilation_units:
                self.compilation_units[file_base] = []
            if func_node.full_name not in self.compilation_units[file_base]:
                self.compilation_units[file_base].append(func_node.full_name)
            
            # Mark this as a potential entry point
            if func_node.full_name not in self.entry_points:
                self.entry_points.append(func_node.full_name)
                self._debug_print(f"Added test function {func_node.full_name} as entry point")
        
        # Parse the function body for calls directly
        match = re.search(r'\{(.*)\}', source_code, re.DOTALL)
        if match:
            func_body = match.group(0)  # Get the full body including braces
            # Extract function calls from this body
            self._parse_function_calls(func_body, func_node)

    def _extract_special_functions(self, all_files):
        """
        Extract functions that might have complex signatures or non-standard patterns.
        Particularly useful for test_* functions, MPI functions, etc.
        """
        # Patterns for complex function signatures
        patterns = [
            # Functions ending with _boundary_comm
            r'void\s+(\w+_boundary_comm)\s*\((?:[^)]*)\)\s*\{',
            # Complex parameter functions
            r'void\s+(\w+)\s*\(\s*float\s*\*\s*(?:[^,)]*),\s*(?:[^)]*)\)\s*\{',
            # Functions with const references
            r'void\s+(\w+)\s*\(\s*(?:const\s+)?(?:[^)]*)\)\s*\{',
            # Functions with specific naming patterns
            r'void\s+(decompose_grid(?:_\w+)?)\s*\((?:[^)]*)\)\s*\{',
            r'void\s+(communicate_\w+_boundaries)\s*\((?:[^)]*)\)\s*\{',
            r'void\s+(print_\w+_grid_\w+)\s*\((?:[^)]*)\)\s*\{',
            # Functions with complex parameters that contain default values
            r'void\s+(\w+)\s*\((?:[^=]*=\s*[^,)]+[^)]*)\)\s*\{',
        ]
        
        for file_path in all_files:
            # Skip files that are not local
            if not self._is_local_file(file_path):
                continue
                
            try:
                with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                    content = f.read()
            except Exception as e:
                self._debug_print(f"Error reading file {file_path}: {e}")
                continue
                
            # Remove comments to simplify parsing
            content = self._remove_comments(content)
            
            # Get the base name of the file
            file_base = os.path.splitext(os.path.basename(file_path))[0]
            
            # Generic pattern to detect functions with default parameters
            default_param_funcs = re.finditer(
                r'(?:void|int|bool|float|double|auto)\s+(\w+)\s*\((?:[^=]*=\s*[^,)]*?(?:,|$))[^)]*\)\s*\{', 
                content, 
                re.MULTILINE
            )
            
            for match in default_param_funcs:
                func_name = match.group(1)
                if func_name.startswith('test_') or 'test_' in func_name:
                    self._debug_print(f"Found function with default parameters: {func_name}")
                    
                    # Skip if we already have this function
                    if func_name in self.functions:
                        # Already have this function, look for the body to extract calls
                        full_match = re.search(
                            rf'{func_name}\s*\([^{{;]*?\)\s*\{{[^{{}}]*(?:\{{[^{{}}]*(?:\{{[^{{}}]*\}}[^{{}}]*)*\}}[^{{}}]*)*\}}',
                            content, 
                            re.DOTALL
                        )
                        
                        if full_match:
                            func_body = full_match.group(0)
                            func_node = self.functions[func_name]
                            self._parse_function_calls(func_body, func_node)
                    else:
                        # Create a new function node
                        func_node = FunctionNode(func_name)
                        self.functions[func_name] = func_node
                        
                        # Add to compilation unit
                        if file_base not in self.compilation_units:
                            self.compilation_units[file_base] = []
                        if func_name not in self.compilation_units[file_base]:
                            self.compilation_units[file_base].append(func_name)
                        
                        # Add to entry points
                        if func_name not in self.entry_points:
                            self.entry_points.append(func_name)
                            
                        # Extract the full function body to parse calls
                        full_match = re.search(
                            rf'{func_name}\s*\([^{{;]*?\)\s*\{{[^{{}}]*(?:\{{[^{{}}]*(?:\{{[^{{}}]*\}}[^{{}}]*)*\}}[^{{}}]*)*\}}',
                            content, 
                            re.DOTALL
                        )
                        
                        if full_match:
                            func_body = full_match.group(0)
                            self._parse_function_calls(func_body, func_node)
            
            # Try each pattern for other functions
            for pattern in patterns:
                matches = re.finditer(pattern, content, re.MULTILINE)
                for match in matches:
                    func_name = match.group(1)
                    
                    # Skip if we already have this function
                    if func_name in self.functions or any(f.endswith(f"::{func_name}") for f in self.functions.keys()):
                        continue
                        
                    # Create a function node
                    func_node = FunctionNode(func_name)
                    
                    # Add to functions dictionary
                    self.functions[func_name] = func_node
                    
                    # Add to compilation unit
                    if file_base not in self.compilation_units:
                        self.compilation_units[file_base] = []
                    if func_name not in self.compilation_units[file_base]:
                        self.compilation_units[file_base].append(func_name)
                    
                    # Look for the full function body to extract calls
                    full_match = re.search(
                        rf'{func_name}\s*\([^{{;]*?\)\s*\{{[^{{}}]*(?:\{{[^{{}}]*(?:\{{[^{{}}]*\}}[^{{}}]*)*\}}[^{{}}]*)*\}}',
                        content, 
                        re.DOTALL
                    )
                    
                    if full_match:
                        func_body = full_match.group(0)
                        self._parse_function_calls(func_body, func_node) 

    def _build_call_graph(self):
        """Build the function call graph after all files have been parsed."""
        self._debug_print("Building function call graph...")
        
        # First identify any remaining missing functions or relationships
        self._identify_special_relationships()
        
        # Find entry points in the code
        self._find_entry_points()
        
        # Verify integrity of the call graph
        self._verify_call_graph()
        
    def _identify_special_relationships(self):
        """
        Identify special function relationships based on patterns in function source code.
        This handles cases where static analysis might miss relationships.
        """
        # Create or ensure the default node exists
        if "default" not in self.functions:
            self._debug_print("Creating default function node")
            default_func = FunctionNode("default")
            self.functions["default"] = default_func
            
        # Ensure test_*_main connects to default
        for func_name, func in self.functions.items():
            if func_name.endswith("_main"):
                if "default" in self.functions:
                    default_func = self.functions["default"]
                    if default_func not in func.calls:
                        self._debug_print(f"Connecting {func_name} to default")
                        func.add_call(default_func)
        
        # Scan all functions for certain patterns
        for func_name, func in self.functions.items():
            if not func.source_code:
                continue
                
            # Find potential function calls in source code that might have been missed
            direct_call_pattern = re.compile(r'(?<![a-zA-Z0-9_])(\w+)\s*\(([^;]*)\)', re.MULTILINE)
            for match in direct_call_pattern.finditer(func.source_code):
                called_func_name = match.group(1)
                
                # Skip if it looks like a control structure, standard function, or push_back
                if (called_func_name in ('if', 'for', 'while', 'switch', 'catch', 'push_back') or 
                    self._is_standard_or_ignored(called_func_name)):
                    continue
                
                # Add relationship to the call graph if the function exists
                if called_func_name in self.functions:
                    called_func = self.functions[called_func_name]
                    func.add_call(called_func)
                    self._debug_print(f"Found direct call relationship: {func_name} -> {called_func_name}")

    def _verify_call_graph(self):
        """Verify the integrity of the call graph."""
        self._debug_print("Verifying call graph integrity...")
        
        # Check for consistency in call relationships
        for func_name, func in list(self.functions.items()):
            for called_func in list(func.calls):
                if func not in called_func.called_by:
                    self._debug_print(f"Warning: Inconsistent call graph relationship - {func_name} calls {called_func.full_name} but not in called_by set")
                    # Fix the relationship
                    called_func.called_by.add(func)
                    
            for calling_func in list(func.called_by):
                if func not in calling_func.calls:
                    self._debug_print(f"Warning: Inconsistent call graph relationship - {calling_func.full_name} should call {func_name} but not in calls set")
                    # Fix the relationship
                    calling_func.calls.add(func)
        
        # Remove push_back from the function call tree if it exists
        if "push_back" in self.functions:
            self._debug_print("Removing push_back from call tree")
            # Get the push_back node
            push_back_node = self.functions["push_back"]
            
            # Remove all references to push_back
            for func_name, func in list(self.functions.items()):
                if push_back_node in func.calls:
                    func.calls.remove(push_back_node)
            
            # Remove the push_back node itself
            del self.functions["push_back"]